/**
 * Copyright (c) 2006 George Malamidis, nutrun.com
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this
 * software and associated documentation files (the "Software"), to deal in the Software
 * without restriction, including without limitation the rights to use, copy, modify,
 * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to the following
 * conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies
 * or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
 * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
 * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
 * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

/**
 * $Author$
 * $Rev$
 */

package aerie.controller;

import aerie.AerieException;
import aerie.context.AerieContext;
import aerie.response.ForwardResponder;
import aerie.response.RedirectResponder;
import aerie.response.RenderResponder;
import aerie.response.Responder;
import aerie.servlet.RequestBinding;
import groovy.util.ResourceException;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.io.Writer;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.*;

public abstract class Controller implements Cloneable {

    private List filters = new ArrayList();

    protected Responder responder;
    protected AerieContext aerieContext;
    protected String decorator;
    protected String action;
    protected String parameter;

    public HttpServletResponse response;
    public HttpServletRequest request;
    public Map params;
    public AerieMessages notify;
    public HttpSession session;
    private RequestBinding requestBinding;

    public void init(RequestBinding binding) throws ResourceException {
        this.requestBinding = binding;
        request = binding.getRequest();
        response = binding.getResponse();
        action = binding.getAction();
        parameter = binding.getParameter();
        populateParamsFromRequest();
        aerieContext = binding.getAerieContext();        
        HttpSession session = request.getSession();
        this.session = session == null ? request.getSession(true) : session;
        notify = getNotify();
    }

    private void populateParamsFromRequest() {
        params = new HashMap();
        Enumeration names = request.getParameterNames();
        String name;
        while (names.hasMoreElements()) {
            name = (String) names.nextElement();
            //noinspection unchecked
            params.put(name, request.getParameter(name));
        }
    }

    public void redirect(String destination) {
        if ( ! hasResponder() ) {
            responder = new RedirectResponder(response, destination);
        }
    }

    public void forward(String destination) {
        if ( ! hasResponder() ) {
            responder = new ForwardResponder(request, response, destination);
        }
    }

    public void render(String destination) throws IOException, ResourceException {
        if ( ! hasResponder() ) {
            Writer writer = response != null ? response.getWriter() : null;
            responder = new RenderResponder(destination, writer, decorator, getAttributes(), requestBinding);
        }
    }

    public boolean redirectsTo(String destination) {
        return (responder instanceof RedirectResponder) && responder.getDestination().equals(destination);
    }

    public boolean forwardsTo(String destination) {
        return (responder instanceof ForwardResponder) && responder.getDestination().equals(destination);
    }

    public boolean renders(String page) {
        return (responder instanceof RenderResponder) && responder.getDestination().equals(page);
    }

    public boolean isDecoratedBy(String decorator) {
        return (null != this.decorator) && this.decorator.equals(decorator);
    }

    public boolean isFilteredBy(String filter) {
        return filters.contains(filter);
    }

    public Map getAttributes() {
        try {
            Map attributes = new HashMap();
            final Field[] fields = getClass().getFields();
            //noinspection ForLoopReplaceableByForEach
            for (int i = 0; i < fields.length; i++) {
                Field field = fields[i];
                //noinspection unchecked
                attributes.put(field.getName(), field.get(this));
            }
            return attributes;
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e.getMessage(), e);
        }
    }

    public Object invokeAction() throws IllegalAccessException, InvocationTargetException {
        String actionName = action;
        try {
            if (parameter == null) {
                actionName += "(no parameters)";
                //noinspection RedundantArrayCreation
                return getClass().getMethod(action, new Class[] {}).invoke(this, new Object[] {});
            } else {
                actionName += "(parameter)";
                //noinspection RedundantArrayCreation
                return getClass().getMethod(action, new Class[] {Object.class}).invoke(this, new Object[] {parameter});
            }
        } catch (NoSuchMethodException e) {
            throw new AerieException("Method [" + actionName + "] not found in Controller [" + getClass().getName() +  "]");
        }
    }

    public Controller clone() throws CloneNotSupportedException {
        return (Controller) super.clone();
    }

    public Responder getResponder() {
        return responder;
    }

    public void setDecorator(String decorator) {
        this.decorator = decorator;
    }

    public List getFilters() {
        return filters;
    }

    public String getAction() {
        return action;
    }

    protected void filterWith(String filter) {
        //noinspection unchecked
        filters.add(filter);
    }

    public String getName() {
        String[] canonicalName = getClass().getCanonicalName().split("\\.");
        return canonicalName[canonicalName.length - 1];
    }

    private boolean hasResponder() {
        return responder != null;
    }

    private AerieMessages getNotify() {
        AerieMessages notify = (AerieMessages) session.getAttribute("notify");
        if (notify != null) {
            this.notify = notify;
        } else {
            this.notify = new AerieMessages();
            session.setAttribute("notify", this.notify);
        }
        return notify;
    }
}
